//
// Created by glyn on 2024/3/15.
//
#pragma once

#include "basedomainsocket.hpp"

#include <string>
#include <string.h>
#include <functional>
#include <thread>

template <uint16_t BUFFER_SIZE = AS_DOMAIN_BUFFER_SIZE>
class StreamDomainSocket : public BaseDomainSocket
{
public:
    // Event Listeners:
    std::function<void(std::string)> onMessageReceived;
    std::function<void(const char*, ssize_t)> onRawMessageReceived;
    std::function<void(int)> onSocketClosed;

    explicit StreamDomainSocket(FDR_ON_ERROR, int socketId = -1) : BaseDomainSocket(onError, TCP, LOCALM,socketId){

    }

    // Send raw bytes
    ssize_t Send(const char* bytes, size_t byteslength) { return send(this->sock, bytes, byteslength, 0); }
    // Send std::string
    ssize_t Send(const std::string& message) { return this->Send(message.c_str(), message.length()); }


    // Connect to a TCP Server with `const char* host` & `uint16_t port` values
    void Connect(const char* path, std::function<void()> onConnected = [](){}, FDR_ON_ERROR)
    {
        this->address.sun_family  = LOCALM;
        strcpy(this->address.sun_path, path);

        this->setTimeout(5);

        // Try to connect.
        int status = connect(this->sock, (const sockaddr *)&this->address, sizeof(sockaddr_un));
        if ( status == -1)
        {
            onError(errno, "Connection failed to the host.");
            this->setTimeout(0);
            return;
        }

        this->setTimeout(0);

        // Connected to the server, fire the event.
        onConnected();

        // Start listening from server:
        this->Listen();
    }
    // Connect to a TCP Server with `const std::string& ipv4` & `uint16_t port` values
    void Connect(const std::string& path, std::function<void()> onConnected = [](){}, FDR_ON_ERROR)
    {
        this->Connect(path.c_str(), onConnected, onError);
    }

    // Start another thread to listen the socket
    void Listen()
    {
        std::thread t(StreamDomainSocket::Receive, this);
        t.detach();
    }

    void setAddressStruct(sockaddr_un addr) {this->address = addr;}
    sockaddr_un getAddressStruct() const {return this->address;}

    bool deleteAfterClosed = false;

private:
    static void Receive(StreamDomainSocket* socket)
    {
        char tempBuffer[BUFFER_SIZE+1];
        ssize_t messageLength;

        while ((messageLength = recv(socket->sock, tempBuffer, BUFFER_SIZE, 0)) > 0)
        {
            tempBuffer[messageLength] = '\0';
            if(socket->onMessageReceived)
                socket->onMessageReceived(std::string(tempBuffer, messageLength));

            if(socket->onRawMessageReceived)
                socket->onRawMessageReceived(tempBuffer, messageLength);
        }

        socket->Close();
        if(socket->onSocketClosed)
            socket->onSocketClosed(errno);

        if (socket->deleteAfterClosed && socket != nullptr)
            delete socket;
    }

    void setTimeout(int seconds)
    {
        struct timeval tv;
        tv.tv_sec = seconds;
        tv.tv_usec = 0;

        setsockopt(this->sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv));
        setsockopt(this->sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
    }
};
